package cmd

import (
	"bufio"
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
)

type Argument struct {
	Script      string   `json:"script"`
	Params      []string `json:"params"`
	WorkDir     string   `json:"work_dir"`
	Envars      []string `json:"envars"`
	Stdin       []byte   `json:"stdin"`
	TempLogFile *os.File `json:"output_file"`
}

type ArgumentStream struct {
	Argument
	StreamCallback       func([]byte) error `json:"-"`
	StreamFinishCallback func()             `json:"-"`
	Output               chan string        `json:"-"`
}

// RunCommand 执行脚本。
// 1. 打印标准输出、标准错误到终端
// 2. 函数返回标准输出和错误
// 3. 若args.TempLogFile非空，则额外将标准输出、标准错误写入文件
func RunCommand(args *Argument) ([]byte, error) {
	return RunCommandContext(context.Background(), args)
}

// RunCommandContext 执行脚本。
// 1. 打印标准输出、标准错误到终端
// 2. 函数返回标准输出和错误
// 3. 若args.TempLogFile非空，则额外将标准输出、标准错误写入文件
func RunCommandContext(ctx context.Context, args *Argument) ([]byte, error) {
	fmt.Printf("RunCommand: %s [%+v]\n", args.Script, strings.Join(args.Params, ","))

	var bout bytes.Buffer
	var berr bytes.Buffer
	stdouts := []io.Writer{&bout, os.Stdout}
	stderrs := []io.Writer{&berr, os.Stderr}
	if args.TempLogFile != nil {
		stdouts = append(stdouts, args.TempLogFile)
		stderrs = append(stderrs, args.TempLogFile)
	}

	cmd := exec.CommandContext(ctx, args.Script, args.Params...)
	cmd.Dir = args.WorkDir
	cmd.Stdout = io.MultiWriter(stdouts...)
	cmd.Stderr = io.MultiWriter(stderrs...)
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, args.Envars...)

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("[ERROR]script:<%s> failed:\n"+
			">> cmd.Run failed:%v\n"+
			">> cmd.Stderr:%s",
			args.Script, err, berr.String())
	}

	return bout.Bytes(), nil
}

// RunCommandStream 在流(chan string)中持续返回标准输出、标准错误
func RunCommandStream(ctx context.Context, args *ArgumentStream) error {
	startAt := time.Now()
	defer func() {
		args.Output <- "work done!"
		fmt.Printf("command exit,time used %s\n", time.Since(startAt))
	}()
	fmt.Printf("RunCommand: %s [%+v]\n", args.Script, strings.Join(args.Params, ","))
	cmd := exec.CommandContext(ctx, args.Script, args.Params...)
	//syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) // 手动发送信号时需要此行代码
	if args.Stdin != nil {
		cmd.Stdin = bytes.NewBuffer(args.Stdin)
	}
	var berr bytes.Buffer
	pr, pw, err := os.Pipe()
	if err != nil {
		return fmt.Errorf("cmd.Start failed:%v\n%s failed:%s", err, args.Script, berr.String())
	}
	pwout := io.MultiWriter(pw, os.Stdout)
	pwerr := io.MultiWriter(pw, os.Stderr)
	cmd.Stdout = pwout
	cmd.Stderr = pwerr
	defer pw.Close()
	//c.closeAfterStart = append(c.closeAfterStart, pw)
	//c.closeAfterWait = append(c.closeAfterWait, pr)
	go func() {
		defer func() {
			log.Println("scanner exit")
		}()
		scanner := bufio.NewScanner(pr)
		for scanner.Scan() {
			b := scanner.Bytes()
			args.Output <- string(b)
		}
	}()
	//if err := cmd.Start(); err != nil {
	//	return fmt.Errorf("cmd.Start failed:%v\n%s failed:%s", err, args.Script, berr.String())
	//}

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("cmd.Wait failed:%v\n%s failed:%s", err, args.Script, berr.String())
	}

	return nil
}

// shell pipe
//	c1 := exec.Command("ls")
//	c2 := exec.Command("wc", "-l")
//
//	r, w := io.Pipe()
//	c1.Stdout = w
//	c2.Stdin = r
//
//	var b2 bytes.Buffer
//	c2.Stdout = &b2
//
//	c1.Start()
//	c2.Start()
//	c1.Wait()
//	w.Close()
//	c2.Wait()
//	io.Copy(os.Stdout, &b2)
